package com.egao.common.core.security;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.egao.common.core.Constants;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;

/**
 * Spring Security配置
 *
 * @author system
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ValidateCodeFilter validateCodeFilter;

    @Order(1)
    @Configuration
    public class Security extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                    .antMatchers(HttpMethod.GET, "/favicon.ico", "/api/file/**", "/", "/assets/**", "/**.html")
                    .permitAll()
                    .antMatchers("/api/login", "/api/login/cas", "/api/login/cas/logout",
                            "/api/minio/**",
                            "/api/publicly/**",
                            "/error", "/druid/**", "/webjars/**", "/swagger-ui.html",
                            "/swagger-resources/**", "/v2/api-docs").permitAll()
                    .anyRequest().authenticated()
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and().headers().frameOptions().disable()
                    .and().cors().and().csrf().disable();
            http.exceptionHandling().accessDeniedHandler(jwtExceptionHandler()).authenticationEntryPoint(jwtExceptionHandler());
            http.logout().logoutUrl("/logout").logoutSuccessHandler(jwtLogoutSuccessHandler());
            http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
            http.addFilterBefore(jwtLoginFilter(), UsernamePasswordAuthenticationFilter.class);
            http.addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class);
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//            auth.userDetailsService(userDetailsServiceImpl()).passwordEncoder(bCryptPasswordEncoder());
            auth.userDetailsService(userDetailsServiceImpl()).passwordEncoder(new PasswordEncoder() {
                @Override
                public String encode(CharSequence charSequence) {
                    return null;
                }

                @Override
                public boolean matches(CharSequence charSequence, String s) {
                    RSA rsa = new RSA(Constants.RSA_PRIVATE_KEY, Constants.RSA_PUBLIC_KEY);
                    byte[] decrypt = rsa.decrypt(charSequence.toString(), KeyType.PrivateKey);
                    String password = StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8);
                    return new BCryptPasswordEncoder().matches(password, s);
                }
            });
        }

        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

        @Bean
        public BCryptPasswordEncoder bCryptPasswordEncoder() {
            return new BCryptPasswordEncoder();
        }

        @Bean
        public UserDetailsServiceImpl userDetailsServiceImpl() {
            return new UserDetailsServiceImpl();
        }

        @Bean
        public JwtLoginFilter jwtLoginFilter() throws Exception {
            return new JwtLoginFilter(authenticationManagerBean());
        }

        @Bean
        public JwtRequestFilter jwtRequestFilter() {
            return new JwtRequestFilter(userDetailsServiceImpl());
        }

        @Bean
        public JwtLogoutSuccessHandler jwtLogoutSuccessHandler() {
            return new JwtLogoutSuccessHandler();
        }

        @Bean
        public JwtExceptionHandler jwtExceptionHandler() {
            return new JwtExceptionHandler();
        }
    }

    @Autowired
    private CasProperties casProperties;

    @Autowired
    private AuthenticationUserDetailsService casUserDetailService;

    @Order(2)
    @Configuration
    public class cas extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                    .antMatchers(HttpMethod.GET, "/favicon.ico", "/api/file/**", "/", "/assets/**", "/**.html")
                    .permitAll()
                    .antMatchers("/api/login", "/api/login/cas", "/api/login/cas/logout",
                            "/api/minio/**",
                            "/api/publicly/**",
                            "/error", "/druid/**", "/webjars/**", "/swagger-ui.html",
                            "/swagger-resources/**", "/v2/api-docs").permitAll()
                    .anyRequest().authenticated()
                    .and().logout().permitAll()
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and().headers().frameOptions().disable()
                    .and().cors().and().csrf().disable();


            http.exceptionHandling()
                    .authenticationEntryPoint(casAuthenticationEntryPoint())
                    .and()
                    .addFilter(casAuthenticationFilter()).
                    //添加单点注销的filter
                            addFilterBefore(logoutFilter(), LogoutFilter.class).
                    addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class);

        }

        @Bean
        public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
            CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
            // 设置CAS服务端登录URL
            casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl());
            // 设置CAS客户端信息
            casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
            return casAuthenticationEntryPoint;
        }

        /**
         * CAS 客户端信息
         *
         * @return
         */
        @Bean
        public ServiceProperties serviceProperties() {
            ServiceProperties serviceProperties = new ServiceProperties();
            // 设置CAS客户端登录完整的URL
            serviceProperties.setService(casProperties.getCasServiceLoginUrl());
            serviceProperties.setAuthenticateAllArtifacts(true);
            return serviceProperties;
        }

        /**
         * CAS 认证过滤器
         * casAuthenticationFilter.setFilterProcessesUrl 必须要设置完整路径 不然会无限重定向
         *
         * @return
         * @throws Exception
         */
        @Bean
        public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
            CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
            casAuthenticationFilter.setAuthenticationManager(authenticationManager());
            casAuthenticationFilter.setFilterProcessesUrl(casProperties.getCasServiceLoginUrl());
            casAuthenticationFilter.setServiceProperties(serviceProperties());
            return casAuthenticationFilter;
        }

        /**
         * CAS 认证Provider
         *
         * @return
         */
        @Bean
        public CasAuthenticationProvider casAuthenticationProvider() {
            CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
            // 设置UserDetailService 登录成功后会进入loadUserDetails方法中
            casAuthenticationProvider.setAuthenticationUserDetailsService(casUserDetailService);
            // 设置CAS客户端消息
            casAuthenticationProvider.setServiceProperties(serviceProperties());
            // 设置CAS票据验证器
            casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
            // 设置CAS密钥
            casAuthenticationProvider.setKey("casAuthenticationProviderKey");
            return casAuthenticationProvider;
        }

        /**
         * CAS 票据验证器
         *
         * @return
         */
        @Bean
        public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
            return new Cas20ServiceTicketValidator(casProperties.getCasServerUrl());
        }

        /**
         * 单点注销过滤器
         * 用于接收CAS服务端的注销请求
         *
         * @return
         */
        @Bean
        public SingleSignOutFilter singleSignOutFilter() {
            SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
            singleSignOutFilter.setCasServerUrlPrefix(casProperties.getCasServerUrl());
            singleSignOutFilter.setIgnoreInitConfiguration(true);
            singleSignOutFilter.setLogoutCallbackPath(casProperties.getCasServiceLoginUrl());
            return singleSignOutFilter;
        }

        /**
         * 单点退出过滤器
         * 用于跳转到CAS服务器
         *
         * @return
         */
        @Bean
        public LogoutFilter logoutFilter() {
            LogoutFilter logoutFilter = new LogoutFilter(casProperties.getCasServerLogoutUrl(), new SecurityContextLogoutHandler());
            logoutFilter.setFilterProcessesUrl("/logout");
            return logoutFilter;
        }

    }

}
